package com.tbit.uqbike.client.controller;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.huifu.adapay.core.AdapayCore;
import com.huifu.adapay.core.util.AdapaySign;
import com.tbit.uqbike.client.aop.charge.ChargeReturnBikeLimit;
import com.tbit.uqbike.client.aop.charge.RechargeTypeParam;
import com.tbit.uqbike.client.aop.charge.TokenParam;
import com.tbit.uqbike.client.config.RefundSwitchConfig;
import com.tbit.uqbike.client.constant.*;
import com.tbit.uqbike.client.constant.annotation.PreventDuplication;
import com.tbit.uqbike.client.dao.log.RefundOrderDao;
import com.tbit.uqbike.client.exception.BaseException;
import com.tbit.uqbike.client.pojo.Recharge;
import com.tbit.uqbike.client.pojo.RefundOrder;
import com.tbit.uqbike.client.pojo.User;
import com.tbit.uqbike.client.pojo.newEntity.AdaPayLog;
import com.tbit.uqbike.client.pojo.newEntity.CallbackDataVo;
import com.tbit.uqbike.client.pojo.newEntity.PayCallbackData;
import com.tbit.uqbike.client.service.*;
import com.tbit.uqbike.client.util.Assert;
import com.tbit.uqbike.client.util.DateTimeUtil;
import com.tbit.uqbike.client.util.OutputUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 汇付天下聚合支付
 *
 * @Author: chen
 * @Description: 汇付天下聚合支付
 * @Date: 创建于 14:47 2021/9/2
 */
@Controller
@RequestMapping("/adapay")
@Slf4j
public class AdaPayController {


    @Autowired
    private RedisService redisService;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private AdaPayService adaPayService;
    @Autowired
    private RechargeService rechargeService;
    @Autowired
    private WeixinPayService weixinPayService;
    @Autowired
    private AdaPayLogService adaPayLogService;
    @Autowired
    private UserAdaPayLogService userAdaPayLogService;
    @Autowired
    private MQProducer mqProducer;
    @Autowired
    private RefundOrderDao refundOrderDao;
    @Resource
    private RefundSwitchConfig refundSwitchConfig;
    @Autowired
    private RefundOrderService refundOrderService;

    /**
     * 回调
     *
     * @return {@link String}
     * @ignoreParams request
     * @ignore
     */
    @PostMapping("/callback")
    @ResponseBody
    public String callback(HttpServletRequest request) {
        try {
            //验签请参data
            String data = request.getParameter("data");
            //验签请参sign
            String sign = request.getParameter("sign");
            //验签标记
            boolean checkSign;
            //验签请参publicKey
            String publicKey = AdapayCore.PUBLIC_KEY;
            log.info("验签请参：data={}sign={}",data,sign);
            //验签
            checkSign = AdapaySign.verifySign(data, sign, publicKey);
            if (checkSign) {
                String type = request.getParameter("type");
                //验签成功逻辑
                log.info("成功返回数据data:" + data);
                JSONObject jsonObject = JSONObject.parseObject(data);

                /**支付成功*/
                if (AdaPayConstant.SUCCEEDED.equals(type)) {
                    log.info("支付成功返回数据data:" + data);
                    String order_no = jsonObject.getString("order_no");
                    Recharge recharge = rechargeService.getByOrderNO(order_no);
                    if (recharge != null && recharge.getState() == 0) {
                        /**查询金额是否和订单一致*/
                        Double rechargeMoney = recharge.getMoney() / 100;
                        Double weixinMoney = Double.valueOf(jsonObject.getString("pay_amt"));
                        log.info("汇通天下订单,订单号=" + order_no + ",订单金额=" + rechargeMoney + "元,现金支付金额=" + weixinMoney + "元");
                        if (!rechargeMoney.equals(weixinMoney)) {
                            log.error("订单金额与支付金额不一致,订单号=" + order_no + ",订单金额=" + rechargeMoney + "元,现金支付金额=" + weixinMoney + "元");
                        } else {
                            String dateTime = DateTimeUtil.getNowTime();
                            if (jsonObject.getString("end_time") != null) {
                                Date date = DateUtil.parse(jsonObject.getString("end_time"));
                                dateTime = DateUtil.formatDateTime(date);
                            }

                            mqProducer.sendPay(recharge.getOrderNO(), jsonObject.get("id").toString(), jsonObject.get("out_trans_id").toString(), dateTime
                                    , jsonObject.getString("fee_amt"));
                        }

                    } else {
                        log.error("没有查询到支付订单，订单号=" + order_no);
                    }
                } else if (AdaPayConstant.CORP_MEMBER_SUCCEEDED.equals(type)) {
                    //开户成功

                } else if (AdaPayConstant.REFUND_SUCCEEDED.equals(type)) {
                    // 新老退款开关
                    if (isRefundOld()){
                        return "200";
                    }
                    // 实时时分账
                    log.info("汇付退款对象退款成功返回数据data:" + data);
                    String confirmId = jsonObject.getString("payment_id"); //todo 三方平台支付确认订单号-confirmId
                    String money = jsonObject.getString("refund_amt"); // 退款金额（必须大于 0），保留两位小数点，如0.10、100.05等
                    String refundNO = jsonObject.getString("refund_order_no"); // 请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
                    String succeedTime = jsonObject.getString("succeed_time"); // 退款成功时间
                    String feeAmt = jsonObject.getString("fee_amt");   // 退款手续费
                    // zzl 这个在延时分账的情况下，取到的不是对应的充值单号
                    String order_no = jsonObject.getString("payment_order_no");

//                    String adapayId = userAdaPayLogService.getByConfirmId(confirmId).get(0).getPaymentId();
                    AdaPayLog adaPayLog = adaPayLogService.getByConfirmId(confirmId);
                    String adaPayId = adaPayLog.getAdaPayId();
                    String rechargeNO = adaPayLog.getOrderNO();
                    Recharge recharge = rechargeService.getByOrderNO(rechargeNO);
                    // 文档地址 https://docs.adapay.tech/api/trade.html#refund-object
                    //退款成功
                    if (!redisService.setNx(RedisConstant.REFUND_LOCK + "callback" + refundNO, RedisConstant.REFUND_LOCK, 60)) {
                        log.warn("获取分布式锁失败{}",refundNO);
                        return "400";
                    }
                    try{
                        RefundOrder refundOrder = refundOrderService.fetchRefundOrderWithRetry(refundNO, 3, 1000);
                        if (Objects.isNull(refundOrder)){
                            log.error("refundorder为空，请排查refundorder插入问题,refundNO={}",refundNO);
                            return "400";
                        }
                        dealRefundSuccess(refundNO,adaPayId,money,succeedTime, recharge.getOrderNO(), feeAmt);
                    }catch (Exception e){
                      log.error("退款异步回调发生异常",e);
                      // 防止外层异常捕获被吞
                      throw new Exception(e);
                    } finally {
                        redisService.del(RedisConstant.REFUND_LOCK + refundNO);
                    }
                } else if (AdaPayConstant.REFUND_FAILED.equalsIgnoreCase(type)) {
                    // 新老退款开关
                    if (isRefundOld()){
                        return "200";
                    }
                    log.info("汇付退款对象退款失败返回数据data:" + data);
                    String refundNO = jsonObject.getString("refund_order_no"); // 请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
                    if (!redisService.setNx(RedisConstant.REFUND_LOCK + "callback" + refundNO, RedisConstant.REFUND_LOCK, 60)) {
                        log.warn("获取分布式锁失败{}",refundNO);
                        return "400";
                    }
                    try {
                        dealRefundFailed(refundNO, jsonObject);
                    }catch (Exception e){
                        log.error("退款异步回调发生异常",e);
                        // 防止外层异常捕获被吞
                        throw new Exception(e);
                    }finally {
                        redisService.del(RedisConstant.REFUND_LOCK + refundNO);
                    }
                } else if (AdaPayConstant.PAYMENT_REVERSE_SUCCEEDED.equals(type)) {
                    // 新老退款开关
                    if (isRefundOld()){
                        return "200";
                    }
                    // 延时分账
                    log.info("汇付支付撤销对象退款成功返回数据data:" + data);
                    String adaPayId = jsonObject.getString("payment_id"); // 退款目标支付对象 id
                    String money = jsonObject.getString("reverse_amt"); // 撤销金额，必须大于0，保留两位小数点，如0.10、100.05等
                    String refundNO = jsonObject.getString("order_no"); // 请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
                    String succeedTime = jsonObject.getString("succeed_time"); // 退款成功时间
                    String feeAmt = null;   // 退款手续费
                    //支付撤销对象返回信息中没有业务支付订单号，从refundorder中去取
                    RefundOrder refundOrder = refundOrderDao.getByRefundNO(refundNO);
                    if (Objects.isNull(refundOrder)){
                        log.error("refundorder为空，请排查refundorder插入问题,refundNO={}",refundNO);
                        return "400";
                    }
                    log.info("支付撤销对象的退款记录对象refundOrder={}", JSON.toJSONString(refundOrder));
                    AdaPayLog adaPayLog = adaPayLogService.getByAdaPayId(adaPayId);
                    String rechargeNO = adaPayLog.getOrderNO();
                    Recharge recharge = rechargeService.getByOrderNO(rechargeNO);
                    //支付撤销成功
                    if (!redisService.setNx(RedisConstant.REFUND_LOCK + "callback" + refundNO, RedisConstant.REFUND_LOCK, 60)) {
                        log.warn("获取分布式锁失败{}",refundNO);
                        return "400";
                    }
                    try{
                        dealRefundSuccess(refundNO,adaPayId,money,succeedTime, recharge.getOrderNO(), feeAmt);
                    }catch (Exception e){
                        log.error("退款异步回调发生异常",e);
                        // 防止外层异常捕获被吞
                        throw new Exception(e);
                    }finally {
                        redisService.del(RedisConstant.REFUND_LOCK + refundNO);
                    }
                } else if (AdaPayConstant.PAYMENT_REVERSE_FAILED.equals(type)) {
                    // 新老退款开关
                    if (isRefundOld()){
                        return "200";
                    }
                    log.info("汇付支付撤销对象退款失败返回数据data:" + data);
                    String refundNO = jsonObject.getString("order_no"); // 请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
                    //支付撤销失败
                    if (!redisService.setNx(RedisConstant.REFUND_LOCK + "callback" + refundNO, RedisConstant.REFUND_LOCK, 60)) {
                        log.warn("获取分布式锁失败{}",refundNO);
                        return "400";
                    }
                    try {
                        dealRefundFailed(refundNO, jsonObject);
                    }catch (Exception e){
                        log.error("退款异步回调发生异常",e);
                        // 防止外层异常捕获被吞
                        throw new Exception(e);
                    }finally {
                        redisService.del(RedisConstant.REFUND_LOCK + refundNO);
                    }
                }
            }
        } catch (Exception e) {
            log.error("异步回调异常", e);
        }
        return "200";
    }

    private void dealRefundSuccess(String refundNO,String paymentId,String refundMoney,String succeedTime,String transactionId,String feeAmt){
        log.info("退款成功后续处理入参：refundNO={},paymentId={},refundMoney={},succeedTime={},transactionId={},feeAmt={}",refundNO,paymentId,refundMoney,succeedTime,transactionId,feeAmt);
        // 记录退款订单信息
        RefundOrder refundOrder = refundOrderService.fetchRefundOrderWithRetry(refundNO, 3, 1000);
        if (Objects.isNull(refundOrder)){
            log.error("refundorder为空，请排查refundorder插入问题,refundNO={}",refundNO);
            return ;
        }
        log.info("数据库查询到的RefundOrder为{},redundNO为{}",refundOrder,refundNO);

        if (Objects.nonNull(refundOrder) && !refundOrder.getMsgSuccess()){
            // 发送MQ消息
            PayCallbackData msgData = new PayCallbackData();
//                            String msgId = String.format("%s_%s", RefundConstant.ADAPAY_ZFB, StringUtil.getRandomString(8));
//                            msgData.setMsgId(msgId);
//                msgData.setFeedback("feedback");
            CallbackDataVo callbackDataVo = new CallbackDataVo();
            callbackDataVo.setPaymentId(paymentId);  //第三方支付id 退款目标支付对象 id
            callbackDataVo.setType(2);  // 类型 1 支付 ， 2 退款
            callbackDataVo.setStatus("SUCCESS");
            callbackDataVo.setOrderNo(refundNO); // 单号（订单号 退单号）
//                            callbackDataVo.setTotalRefundMoney(String.valueOf(totalRefundMoney));
            callbackDataVo.setRefundMoney(refundMoney); // 退款金额
            callbackDataVo.setConfirmTime(succeedTime);   // 支付时间  退款时间
            callbackDataVo.setTransactionId(transactionId);  // 平台交易号 商户系统所退订单的订单充值号
            callbackDataVo.setFee_amt(feeAmt);
            msgData.setData(callbackDataVo);
            // 记录MQ消息内容
//                            refundOrder.setMessageId(msgId);
            refundOrder.setMessageContent(JSON.toJSONString(msgData));
            refundOrder.setRouteKey(RabbitMQData.orderkey);
            refundOrder.setExchange(RabbitMQData.orderexchange);
            // 设置消息消费状态为未消费
            refundOrder.setMsgSuccess(false);

            // 修改订单退款状态为成功
            refundOrder.setRefundStatus(RefundConstant.REFUND_SUCCESS);
            // 跟新退款订单状态
            refundOrderDao.updateCallBackInfo(refundOrder);
            log.info("发送MQ消息{}",JSON.toJSONString(msgData));
            mqProducer.sendToRide(RabbitMQData.orderexchange,RabbitMQData.orderkey,JSON.toJSONString(msgData));
        }
    }

    private void dealRefundFailed(String refundNO,JSONObject jsonObject){
        // 更新退款订单表refundOrder为退款失败
        // 记录退款订单信息
        RefundOrder refundOrder = refundOrderService.fetchRefundOrderWithRetry(refundNO, 3, 1000);
        if (Objects.isNull(refundOrder)){
            log.error("refundorder为空，请排查refundorder插入问题,refundNO={}",refundNO);
            return ;
        }
        log.info("数据库查询到的RefundOrder为{},redundNO为{}",refundOrder,refundNO);
        if (Objects.nonNull(refundOrder)){
            // 修改订单退款状态为失败
            refundOrder.setRefundStatus(RefundConstant.REFUND_FAIL);
            // 修改错误信息
            refundOrder.setErrorMsg(jsonObject.getString("error_msg"));
            // 跟新退款订单状态
            refundOrderDao.updateCallBackInfo(refundOrder);
        }
    }

    /**
     * 创建支付订单
     *
     * @param token        令牌
     * @param adAccountId  区域id
     * @param code         代码
     * @param type         类型
     * @param remark       备注
     * @param deposit      是否押金
     * @param rechargeType 充值类型
     * @param dispatchType 调度类型
     * @param response     响应
     * @ignoreParams request
     */
    @RequestMapping("/createOrder")
    @PreventDuplication(expireSeconds = 5)
    @ChargeReturnBikeLimit
    public void createOrder_wx_xcx(@TokenParam String token, Integer adAccountId, String code, Double money, @RequestParam(defaultValue = "wx_lite") String type, String remark, Boolean deposit, @RequestParam(defaultValue = "0") @RechargeTypeParam Integer rechargeType, Integer dispatchType,
                                   HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {

            if (deposit == null) {
                deposit = false;
            }
            User user = tokenService.getUser(token);
            if (deposit) {
                remark = "押金";
                Assert.notNull(adAccountId, "-10001", "区域id不能为空");
            }
            if (!deposit) {
                /* 校验 */
                Assert.notNull(money, "-70001", "充值金额不能为空");
                if (adAccountId != null) {
                    redisService.add(user.getUserId() + ".agentId", String.valueOf(adAccountId), 600);
                }

            }
            /**充值结束订单需要校验下用户点击还车检验到创建订单时长，超出1分钟不许还车*/
            if (rechargeType.equals(RechargeConstant.recharge_returnBike)) {
                String no = redisService.get(RedisConstant.USER_FINISH_PAY + user.getUserId());
                if (no == null) {
                    throw new BaseException("-20018", "订单超时，请重试");
                }
                remark = "充值结束订单";
            }
            if (remark == null) {
                remark = "充值";
            }
            /**存储调度类型还车*/
            if (dispatchType != null) {
                redisService.add(RedisConstant.REDIS_ACCOUNT_USER_DISPATCH + user.getUserId(), String.valueOf(dispatchType), 90);
            }
            Map<String, Object> dataMap;
            if (AdaPayConstant.PAY_CHANNEL_WXXCX.equals(type)) {
                dataMap = adaPayService.createOrder_wx_xcx(user, money, code, remark, deposit, adAccountId, rechargeType);
            } else {
                dataMap = adaPayService.createOrder_zfb_xcx(user, money, code, remark, deposit, adAccountId, rechargeType);
            }


            map.put(ResponseCode.RET, ResponseCode.SUCC);
            map.put(ResponseCode.DATA, dataMap);
        } catch (BaseException e) {
            ResponseCode.printException(map, e, request);
        }
        OutputUtil.renderText(request, response, map);
    }


    /**
     * 创建免押会员订单_小程序
     *
     * @param token       令牌
     * @param code        代码
     * @param adAccountId 区域id
     * @param memberFeeId 会员费用id
     * @param type        类型
     * @param response    响应
     * @ignoreParams request
     */
    @RequestMapping("/createMemberOrder_wx_xcx")
    @PreventDuplication(expireSeconds = 5)
    public void createMemberOrder_wx_xcx(String token, String code, Integer adAccountId, Integer memberFeeId, @RequestParam(defaultValue = "wx_lite") String type,
                                         HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            User user = tokenService.getUser(token);
            Assert.hasLength(code, "-3027", "微信code不能为空");
            Assert.notNull(memberFeeId, "-120004", "memberFeeId不能为空");
            Assert.notNull(adAccountId, "-10001", "区域id不能为空");

            /**放入redis*/
            redisService.add(user.getUserId() + ".agentId", String.valueOf(adAccountId), 600);
            Map<String, Object> dataMap;
            if (AdaPayConstant.PAY_CHANNEL_WXXCX.equals(type)) {
                dataMap = adaPayService.getMemberOrder_wx_xcx(code, user, memberFeeId, adAccountId);
            } else {
                dataMap = adaPayService.getMemberOrder_zfb_xcx(code, user, memberFeeId, adAccountId);
            }

            map.put(ResponseCode.RET, ResponseCode.SUCC);
            map.put(ResponseCode.DATA, dataMap);
        } catch (BaseException e) {
            ResponseCode.printException(map, e, request);
        }

        OutputUtil.renderText(request, response, map);
    }


    /**
     * 创建骑行会员订单_小程序
     *
     * @param token       令牌
     * @param code        代码
     * @param adAccountId 区域id
     * @param rideCardId  骑行卡id
     * @param type        类型
     * @param response    响应
     * @ignoreParams request
     */
    @RequestMapping("/createRideCardOrder_wx_xcx")
    @PreventDuplication(expireSeconds = 5)
    public void createRideCardOrder_wx_xcx(String token, String code, Integer adAccountId, Integer rideCardId, @RequestParam(defaultValue = "wx_lite") String type,
                                           HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            User user = tokenService.getUser(token);
            Assert.hasLength(code, "-3027", "微信code不能为空");
            Assert.notNull(rideCardId, "-120001", "骑行会员卡id不能为空");
            Assert.notNull(adAccountId, "-10001", "区域id不能为空");
            Map<String, Object> dataMap;
            if (AdaPayConstant.PAY_CHANNEL_WXXCX.equals(type)) {
                dataMap = adaPayService.getRideCardOrder_wx_xcx(code, user, rideCardId, adAccountId);
            } else {
                dataMap = adaPayService.getRideCardOrder_zfb_xcx(code, user, rideCardId, adAccountId);
            }
            map.put(ResponseCode.RET, ResponseCode.SUCC);
            map.put(ResponseCode.DATA, dataMap);
        } catch (BaseException e) {
            ResponseCode.printException(map, e, request);
        }

        OutputUtil.renderText(request, response, map);
    }


    /**
     * 创建会员卡订单_小程序
     *
     * @param token       令牌
     * @param code        代码
     * @param adAccountId 区域id
     * @param vipId       会员卡id
     * @param type        类型
     * @param response    响应
     * @ignoreParams request
     */
    @RequestMapping("/createVipOrder_wx_xcx")
    @PreventDuplication(expireSeconds = 5)
    public void createVipOrder_wx_xcx(String token, String code, Integer adAccountId, Integer vipId, @RequestParam(defaultValue = "wx_lite") String type,
                                      HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            User user = tokenService.getUser(token);
            Assert.hasLength(code, "-3027", "微信code不能为空");
            Assert.notNull(vipId, "-150001", "vipId不能为空");
            Assert.notNull(adAccountId, "-10001", "区域id不能为空");
            Map<String, Object> dataMap;
            if (AdaPayConstant.PAY_CHANNEL_WXXCX.equals(type)) {
                dataMap = adaPayService.getVipOrder_wx_xcx(code, user, vipId, adAccountId);
            } else {
                dataMap = adaPayService.getVipOrder_zfb_xcx(code, user, vipId, adAccountId);
            }
            map.put(ResponseCode.RET, ResponseCode.SUCC);
            map.put(ResponseCode.DATA, dataMap);
        } catch (BaseException e) {
            ResponseCode.printException(map, e, request);
        }

        OutputUtil.renderText(request, response, map);
    }


    /**
     * 创建优惠券套餐订单_小程序
     *
     * @param token       令牌
     * @param code        代码
     * @param adAccountId 区域id
     * @param mealId      优惠券套餐id
     * @param type        类型
     * @param response    响应
     * @ignoreParams request
     */
    @RequestMapping("/createMeal_wx_xcx")
    @PreventDuplication(expireSeconds = 5)
    public void createMeal_weixin(String token, String code, Integer adAccountId, Integer mealId, @RequestParam(defaultValue = "wx_lite") String type,
                                  HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            User user = tokenService.getUser(token);
            Assert.hasLength(code, "-3027", "微信code不能为空");
            Assert.notNull(mealId, "-180001", "主键id不能为空");
            Assert.notNull(adAccountId, "-10001", "区域id不能为空");
            Map<String, Object> dataMap;
            if (AdaPayConstant.PAY_CHANNEL_WXXCX.equals(type)) {
                dataMap = adaPayService.getMeal_wx_xcx(code, user, mealId, adAccountId);
            } else {
                dataMap = adaPayService.getMeal_zfb_xcx(code, user, mealId, adAccountId);
            }
            map.put(ResponseCode.RET, ResponseCode.SUCC);
            map.put(ResponseCode.DATA, dataMap);
        } catch (BaseException e) {
            ResponseCode.printException(map, e, request);
        }

        OutputUtil.renderText(request, response, map);
    }

    /**
     * 判断nacos配置是否新退款接口
     * @return
     */
    private boolean isRefundOld(){
        String refundModel = refundSwitchConfig.getRefundModel();
        return !Objects.equals(refundModel, RefundSwitchConfig.REFUND_NEW);
    }
}
